#include <filesystem>
#include <iostream>
#include <string>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osg/Node>
#include <osg/Group>
#include <osg/PagedLOD>
#include <osg/NodeVisitor>
#include <osgUtil/Optimizer> 
#include <osgUtil/Simplifier> 
#include <osg/Material>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/Texture2D>
#include <osg/Texture3D>

osg::Node* findNamedNode(const std::string& searchName, osg::Node* currNode);
namespace fs = std::filesystem;
class InfoVisitor : public osg::NodeVisitor
{
    std::string path;
    std::string file_name;
public:
    InfoVisitor(std::string _path,std::string _file_name)
    :osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
    ,path(_path)
    ,file_name(_file_name)
    {
        find = false;
    }
    ~InfoVisitor() {
    }
    void apply(osg::Geode& node)
    {
        geode_array.push_back(&node);
        traverse(node);
    }
    void apply(osg::PagedLOD& node) {
        //std::string path = node.getDatabasePath();
        int n = node.getNumFileNames();
        for (size_t i = 1; i < n; i++)
        {
            std::string file_name = path + "/" + node.getFileName(i);
            sub_node_names.push_back(file_name);
        }
        traverse(node);
    }
    void apply(osg::Group& group)
    {
        unsigned int num = group.getNumChildren();
        if(num > 1){
            find = true;
        }
        for (unsigned int i = 0; i < num; i++)
        {
            osg::Node* node = group.getChild(i);
            osg::PagedLOD* lod = dynamic_cast<osg::PagedLOD*>(node);
            lod_array.push_back(lod);          
        }
    }
public:
    bool find;
    std::vector<osg::Geode*> geode_array;
    std::vector<osg::PagedLOD*> lod_array;
    std::vector<std::string> sub_node_names;
};

std::string get_parent(std::string str) {
    auto p0 = str.find_last_of("/\\");
    if (p0 != std::string::npos)
        return str.substr(0, p0);
    else
        return "";
}
void addChild(std::string target_dir_, osg::Geode* geode){
    for (int i = 0; i < geode->getNumDrawables(); i++)
    {
        osg::Geometry* geometry = dynamic_cast<osg::Geometry*>(geode->getDrawable(i));
        if (geometry)
        {
            //纹理
            osg::Texture2D* tex2D = dynamic_cast<osg::Texture2D*>(geometry->getStateSet()->getTextureAttribute(0, osg::StateAttribute::TEXTURE));
            osg::Image* image = tex2D->getImage();
            std::string jpgfileName =  image->getFileName();
            osgDB::writeImageFile(*image, target_dir_ + "/" + jpgfileName);
            int width = image->s();
            int height = image->t();
        }
    }
}
void do_osg_tree( std::string& file_name, std::string target_dir_, osg::Group* root) {
    std::vector<std::string> fileNames = { file_name };
    std::string parent_path = get_parent(file_name);
    std::string tile_name = fs::path(file_name).stem().string();
    osg::ref_ptr<osg::Node> node = osgDB::readNodeFiles(fileNames);
    if (!node.valid()) {
        return;
    }
    InfoVisitor infoVisitor(parent_path, parent_path + "/" + tile_name +".osgb");
    node->accept(infoVisitor);
    if (infoVisitor.lod_array.empty()) return;

    int pnum = 0;
    for (auto child : infoVisitor.lod_array) {
        InfoVisitor irChild(parent_path, parent_path + "/" + tile_name +"_"+ std::to_string(pnum) +".osgb");
        child->accept(irChild);
        for (auto geode : irChild.geode_array) {
            root->addChild(geode);
            addChild(target_dir_,geode);
        }
        pnum++;
    }
}

void get_all_tree( std::string& file_name, std::string target_dir_, osg::Group* root) {
    std::vector<std::string> fileNames = { file_name };        
    InfoVisitor infoVisitor(get_parent(file_name),file_name);
    {   // add block to release Node
        osg::ref_ptr<osg::Node> node = osgDB::readNodeFiles(fileNames);
        if (!node) {
            return ;
        }
        node->accept(infoVisitor);
    }
    if(infoVisitor.sub_node_names.size() > 0 && !infoVisitor.find){
        for (auto& i : infoVisitor.sub_node_names) {
            get_all_tree(i,target_dir_,root);
        }
    }else if(infoVisitor.find){
        do_osg_tree(file_name,target_dir_,root);
    }
}

void process(std::string source_dir_, std::string target_dir_, osg::Group* root){
    for (const auto &data_dir: fs::directory_iterator(source_dir_,
            ~fs::directory_options::follow_directory_symlink
            | fs::directory_options::skip_permission_denied)) {
        if(!fs::is_directory(data_dir.path()))
            continue; 
        std::string tile_name = data_dir.path().stem().string();
        std::string tile_path = (data_dir.path() / (tile_name + ".osgb")).string();
        if(!fs::exists(tile_path)){
            process(data_dir.path().string(),(fs::path(target_dir_) / fs::path(tile_name)).string(),root);
            continue;
        }
        std::cout << "find data " << data_dir<<", start process tile" << std::endl;
        std::string tile_dir = data_dir.path().string();
        std::string out_dir = (fs::path(target_dir_) / fs::path(tile_name)).string();
        if (!fs::exists(out_dir)) {
            fs::create_directories(out_dir);
        }
        fs::path file_path{tile_path};
        std::string osgb_tile{std::move(tile_path)};
        get_all_tree(osgb_tile,out_dir,root);

    }
}

int main(int argc, char* argv[])
{

    // Check command-line parameters
    if (argc < 3)
    {
    std::cerr << "Usage: " << argv[0] << " <model files> ... <name of output file>\n";
    exit(1);
    }
    if (!fs::is_directory(std::string( argv[1]))){
        std::cerr << "model files: " << argv[1] << " not exist\n";
        exit(1);
    }
    std::string source_dir_ = std::string( argv[1]) ;
    std::string target_dir_ = std::string( argv[argc-1]) ;
    if (source_dir_.back() == '/') {
        source_dir_.pop_back(); 
    }
    std::string model_file = fs::path(source_dir_).stem().string();
    std::string out_dir = (fs::path(target_dir_) / fs::path(model_file)).string();
    if (!fs::exists(out_dir)) {
        fs::create_directories(out_dir);
    }
    osg::Group* root = new osg::Group();
    process(source_dir_,out_dir,root);

    if(osgDB::writeNodeFile( *root, ""+ (fs::path(out_dir) / ("Data.osgb")).string(),new osgDB::Options("WriteImageHint=IncludeData Compressor=zlib"))){
        std::cout << "Merging has been successfully done\n";	
    }else{
        std::cout << "Merging has been failed!\n";
    }

    return 0;
}
